<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>6.画贝塞尔曲线（绘制不规则的图形）</title>
    <style>
        canvas{
            border:1px solid #aaa;
            margin: 0 auto;
            display: block;
        }
    </style>
</head>
<body>
    <canvas width="800" height="800"></canvas>

    <script>
        let canvas = document.querySelector("canvas");
        let ctx = canvas.getContext("2d")

        // 1.设置坐标，坐标变换
        ctx.translate(canvas.width/2, canvas.height/2);//将坐标原点平移到画布最底部
        ctx.scale(1,-1);//翻转y轴，让y轴朝上
        ctx.lineCap = 'round';// 定义线条末端线帽的样式
        
        /**
         * 2.设置向量
         * （1）设置x轴基向量(1,0)
         * （2）设置实际向量：
         *      a.设置x轴基向量一个旋转弧度r，这时基向量的坐标为（x*cosr-y*sinr,y*cosr+x*sinr）,动静坐标转换得到的公式
         *      b.将旋转后的向量，放大L倍，坐标为L（x*cosr-y*sinr,y*cosr+x*sinr）
         */
        class Vector2D extends Array {
            // 生成坐标向量
            constructor(x = 1, y = 0) {
                super(x, y);
            }
            set x(v) {
                this[0] = v;
            }
            set y(v) {
                this[1] = v;
            }
            get x() {
                return this[0];
            }
            get y() {
                return this[1];
            }
            get length() {
                // 计算向量的模
                return Math.hypot(this.x, this.y);
            }
            get dir() {
                // 计算向量的旋转的弧度
                return Math.atan2(this.y, this.x);
            }
            copy() {
                // 复制坐标向量
                return new Vector2D(this.x, this.y);
            }
            // 坐标向量相加
            add(v) {
                this.x += v.x;
                this.y += v.y;
                return this;
            }
            // 坐标向量相减
            sub(v) {
                this.x -= v.x;
                this.y -= v.y;
                return this;
            }
            // 倍数延长坐标向量
            scale(a) {
                this.x *= a;
                this.y *= a;
                return this;
            }
            // 向量叉乘的模
            cross(v) {
                return this.x * v.y - v.x * this.y;
            }
            // 向量点乘的值
            dot(v) {
                return this.x * v.x + v.y * this.y;
            }
            // 向量归一化（向量除以向量的模）
            normalize() {
                return this.scale(1 / this.length);
            }
            // 向量旋转
            rotate(rad) {
                const c = Math.cos(rad),
                s = Math.sin(rad);
                const [x, y] = this;

                this.x = x * c + y * -s;
                this.y = x * s + y * c;

                return this;
            }
        }
        

        
        // 根据点来绘制图形
        function draw(points, context, {
            strokeStyle = 'black',
            fillStyle = null,
            close = false,
        } = {}) {
            context.strokeStyle = strokeStyle;
            context.beginPath();
            context.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                context.lineTo(...points[i]);
            }
            if(close) context.closePath();
            if(fillStyle) {
                context.fillStyle = fillStyle;
                context.fill();
            }
            context.stroke();
        }
        // 高阶函数
        function parametric(xFunc, yFunc) {
            return function (start, end, seg = 100, ...args) {
                const points = [];
                for(let i = 0; i <= seg; i++) {
                    const p = i / seg;
                    const t = start * (1 - p) + end * p;
                    const x = xFunc(t, ...args); // 计算参数方程组的x
                    const y = yFunc(t, ...args);  // 计算参数方程组的y
                    points.push([x, y]);
                }
                return {
                    draw: draw.bind(null, points),
                    points,
                };
            };
        }
        
        // 二阶贝塞尔曲线公式：Bt = (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2   （0<=t<=1） P0是起点，P1是控制点，P2是终点
        // 1.二阶贝塞尔曲线 
        // const quadricBezier = parametric(
        //     (t, [{x: x0}, {x: x1}, {x: x2}]) => (1 - t) ** 2 * x0 + 2 * t * (1 - t) * x1 + t ** 2 * x2,
        //     (t, [{y: y0}, {y: y1}, {y: y2}]) => (1 - t) ** 2 * y0 + 2 * t * (1 - t) * y1 + t ** 2 * y2,
        // );

        // const p0 = new Vector2D(0, 0);
        // const p1 = new Vector2D(100, 0);
        // p1.rotate(0.75);
        // const p2 = new Vector2D(200, 0);
        // const count = 30;
        // for(let i = 0; i < count; i++) {
        //     // 绘制30条从圆心出发，旋转不同角度的二阶贝塞尔曲线
        //     p1.rotate(2 / count * Math.PI);
        //     p2.rotate(2 / count * Math.PI);
        //     quadricBezier(0, 1, 100, [
        //         p0,
        //         p1,
        //         p2,
        //     ]).draw(ctx);
        // }

        // 三阶贝塞尔曲线公式：Bt = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)^2*P2 + t^3*P3   （0<=t<=1） P0和 P3是起点和终点，P1、P2是控制点
        // 2.三阶贝塞尔曲线 
        const cubicBezier = parametric(
        (t, [{x: x0}, {x: x1}, {x: x2}, {x: x3}]) => (1 - t) ** 3 * x0 + 3 * t * (1 - t) ** 2 * x1 + 3 * (1 - t) * t ** 2 * x2 + t ** 3 * x3,
        (t, [{y: y0}, {y: y1}, {y: y2}, {y: y3}]) => (1 - t) ** 3 * y0 + 3 * t * (1 - t) ** 2 * y1 + 3 * (1 - t) * t ** 2 * y2 + t ** 3 * y3,
        );

        const p0 = new Vector2D(0, 0);
        const p1 = new Vector2D(100, 0);
        p1.rotate(0.75);
        const p2 = new Vector2D(150, 0);
        p2.rotate(-0.75);
        const p3 = new Vector2D(200, 0);
        const count = 30;
        for(let i = 0; i < count; i++) {
            p1.rotate(2 / count * Math.PI);
            p2.rotate(2 / count * Math.PI);
            p3.rotate(2 / count * Math.PI);
            cubicBezier(0, 1, 100, [
                p0,
                p1,
                p2,
                p3,
            ]).draw(ctx);
        }

    </script>
</body>
</html>